import {
  CMEVENTS,
  CSPoint,
  pickedFaceData,
  pickedLineData,
  pickedVertexData,
  POINT_TYPE,
} from '@amcax/base';
import { ctx2, dataManager, userEvents } from '@app-cad/module';
import { MARK_EVENT } from '@app-cad/common/Constants';
import { MarkComponentOrigin } from '@app-cad/common/widgets/RightNavigation/MarksPanel';
import MarkInput from '@app-cad/common/widgets/RightNavigation/MarksPanel/MarkInput';
import MarksItem from '@app-cad/common/widgets/RightNavigation/MarksPanel/MarksItem';
import { Mark } from '@app-cad/types';
import { useMemoizedFn } from 'ahooks';
import { useEffect, useMemo, useRef, useState } from 'react';
import { createPortal } from 'react-dom';
import { Vector3 } from 'three-legacy';
import cx from 'classnames';
import {
  markManager,
  MarkRenderInfo,
} from '@app-cad/common/managers/MarkManager';
import { CSS2DMarkObject } from '@app-cad/common/managers/CSS2DMarkObject';

type NewMarkParams = {
  pickedObj: pickedVertexData | pickedLineData | pickedFaceData;
  position: Vector3;
};

const getColorFromId = (id: number) => [
  ((id >> 16) & 255) / 255,
  ((id >> 8) & 255) / 255,
  (id & 255) / 255,
];

export const MarkRenderer = () => {
  const [points, setPoints] = useState<MarkRenderInfo[]>([]);
  const [newMarkParams, setNewMarkParams] = useState<NewMarkParams>();

  const [activeKey, setActiveKey] = useState<string>();

  const onRenderMark = useMemoizedFn((map: Map<string, MarkRenderInfo>) => {
    setPoints(Array.from(map.values()));
  });

  const newMarkMarkObject = useRef<CSS2DMarkObject>();

  const newMarkColorID = useRef<number>();
  const pickTagRef = useRef<CSPoint>();

  const onNewMark = useMemoizedFn((params: NewMarkParams) => {
    if (pickTagRef.current != null) {
      pickTagRef.current.destroy();
      pickTagRef.current = null;
    }

    ctx2.cadScene.toolsGroup.remove(newMarkMarkObject.current);
    if (params != null) {
      if (newMarkColorID.current == null) {
        newMarkColorID.current = ++dataManager.colorId;
      }
      const tag = new CSS2DMarkObject(document.createElement('div'));
      tag.position.set(params.position.x, params.position.y, params.position.z);
      tag.colorID = newMarkColorID.current;
      newMarkMarkObject.current = tag;

      const pickTag = new CSPoint({
        pointType: POINT_TYPE.PICK,
        size: 2,
        depthTest: true,
      });
      pickTag.setVertexs(
        [params.position.x, params.position.y, params.position.z],
        getColorFromId(tag.colorID),
      );

      ctx2.viewer.sceneSetup.pickScene.add(pickTag);
      ctx2.cadScene.toolsGroup.add(tag);
      ctx2.viewer.sceneSetup.updatePixelBuffer();

      pickTagRef.current = pickTag;
    }

    // tag被加入DomTree之后再setState
    setTimeout(() => {
      setNewMarkParams(params);
    });
  });

  useEffect(() => {
    userEvents.on(MARK_EVENT.MARK_RENDER_CHANGED, onRenderMark);
    userEvents.on(MARK_EVENT.NEW_MARK, onNewMark);

    return () => {
      userEvents.off(MARK_EVENT.MARK_RENDER_CHANGED, onRenderMark);
      userEvents.off(MARK_EVENT.NEW_MARK, onNewMark);
    };
  });

  return (
    <div>
      {points.map((e) => {
        if (!e.active) {
          return null;
        }

        const key = markManager.getMarkUnionId(e.mark);

        return (
          <Tag
            key={key}
            onActive={() => {
              setActiveKey(key);
            }}
            active={key === activeKey}
            mark={e.mark}
            relatedTag={e.tag}
          />
        );
      })}
      {newMarkParams != null && (
        <Tag
          key='new'
          active={activeKey === 'new'}
          onActive={() => {
            setActiveKey('new');
          }}
          relatedTag={newMarkMarkObject.current}
          newMarkParams={newMarkParams}
          onClose={() => {
            setNewMarkParams(null);
          }}
        />
      )}
    </div>
  );
};

const TAG_WIDTH = 236;
const LEFT_EDGE_OFFSET = 100;
const RIGHT_EDGE_OFFSET = 400;

const Tag = ({
  mark,
  newMarkParams,
  relatedTag,
  onClose,
  active,
  onActive,
}: {
  mark?: Mark;
  newMarkParams?: NewMarkParams;
  relatedTag: CSS2DMarkObject;
  onClose?: VoidFunction;
  active?: boolean;
  onActive?: VoidFunction;
}) => {
  const [expand, setExpand] = useState(newMarkParams != null);
  const [position, setPosition] = useState<{ x: number; y: number }>();
  const [rootPosition, setRootPosition] = useState<{ x: number; y: number }>();

  const [isTagPointVisible, setIsTagPointVisible] = useState(false);

  const updateRootPosition = (element: HTMLDivElement) => {
    const reg = /translate\(-50%, -50%\) translate\((.+)px, (.+)px\)/;
    const [x, y] = element.style.transform
      .replace(reg, '$1,$2')
      .split(',')
      .map(Number);

    setRootPosition({ x, y });

    return { x, y };
  };

  const mutationCallback = useMemoizedFn<MutationCallback>((mutationRecord) => {
    if (expand) {
      const mutationResult = mutationRecord[0];
      updateRootPosition(mutationResult.target as HTMLDivElement);
    }
  });

  const observersRef = useRef<MutationObserver>(
    new MutationObserver(mutationCallback),
  );

  useEffect(() => {
    observersRef.current.disconnect();

    observersRef.current.observe(relatedTag.element, {
      attributes: true,
      attributeFilter: ['style'],
    });
  }, [relatedTag.element]);

  const initPosition = () => {
    const fullWidth = document.body.clientWidth;

    const { x, y } = updateRootPosition(relatedTag.element as HTMLDivElement);

    if (x < fullWidth / 2) {
      const xResult = Math.max(LEFT_EDGE_OFFSET + TAG_WIDTH / 2, x - 500);
      setPosition({ x: xResult, y });
    } else {
      const xResult = Math.min(
        fullWidth - RIGHT_EDGE_OFFSET - TAG_WIDTH / 2,
        x + 500,
      );
      setPosition({ x: xResult, y });
    }
  };
  const checkVisibility = useMemoizedFn(() => {
    const result = relatedTag.checkVisibility();

    setIsTagPointVisible(result);
  });

  useEffect(() => {
    userEvents.on(CMEVENTS.PIXEL_DATA_UPDATED, checkVisibility);

    if (expand) {
      initPosition();
    } else {
      setPosition(null);
    }

    checkVisibility();
  }, [expand, newMarkParams == null]);

  const { lineRectWidth, lineRectHeight } = useMemo(
    () => ({
      lineRectWidth: position?.x - rootPosition?.x,
      lineRectHeight: position?.y - rootPosition?.y,
    }),
    [rootPosition, position],
  );

  const dragStartRef = useRef<{
    x: number;
    y: number;
    startX: number;
    startY: number;
  }>();

  const dialogRef = useRef();

  const onDrag = useMemoizedFn((e: MouseEvent) => {
    if (dragStartRef.current == null) {
      return;
    }
    setPosition({
      x: dragStartRef.current.startX + e.clientX - dragStartRef.current.x,
      y: dragStartRef.current.startY + e.clientY - dragStartRef.current.y,
    });
  });

  const onRelease = useMemoizedFn(() => {
    dragStartRef.current = null;
  });

  useEffect(() => {
    if (expand) {
      document.addEventListener('mousemove', onDrag);
      document.addEventListener('mouseup', onRelease);
    }

    return () => {
      document.removeEventListener('mousemove', onDrag);
      document.removeEventListener('mouseup', onRelease);
    };
  }, [expand]);

  return (
    <>
      {expand && position != null && (
        <div
          ref={dialogRef}
          className={cx('flex fixed', active ? 'z-[1000]' : 'z-[900]')}
          style={{
            transform: `translate(-50%, -50%) translate(${position.x}px, ${position.y}px)`,
          }}
          onClick={() => {
            if (mark != null) {
              userEvents.emit(MARK_EVENT.MARK_SELECTED, mark);
            }
          }}
          onMouseDown={(e) => {
            onActive?.();
            if (
              (e.target as HTMLElement).classList.contains(
                'mark-input__text-area',
              )
            ) {
              return;
            }

            dragStartRef.current = {
              x: e.clientX,
              y: e.clientY,
              startX: position.x,
              startY: position.y,
            };
          }}
        >
          {expand && mark != null && (
            <MarksItem
              onClose={() => {
                setExpand(false);
              }}
              mark={mark}
              style={{
                width: TAG_WIDTH,
                userSelect: 'none',
              }}
              source={MarkComponentOrigin.Float}
            />
          )}
          {newMarkParams != null && (
            <div className='bg-white rounded-lg border-solid border-[2px] border-gray-3 select-none'>
              <MarkInput
                pickedObject={newMarkParams.pickedObj}
                onClose={onClose}
                afterSubmit={onClose}
                source={MarkComponentOrigin.Float}
                position={newMarkParams.position}
              />
            </div>
          )}
        </div>
      )}
      {createPortal(
        !expand && mark != null ? (
          <div
            className='w-5 h-5 cursor-pointer'
            onClick={() => {
              setExpand(true);
            }}
          >
            <svg
              width='20px'
              height='20px'
              fill='currentColor'
              stroke='currentColor'
            >
              <use href='#commands/common/mark-canvas' />
            </svg>
          </div>
        ) : (
          rootPosition != null && position != null && (
            <div
              style={{
                position: 'absolute',
                height: 1,
                border: [
                  isTagPointVisible ? 'solid' : 'dashed',
                  '1px black',
                ].join(' '),
                opacity: isTagPointVisible ? 1 : 0.3,
                width: Math.sqrt(lineRectHeight ** 2 + lineRectWidth ** 2),
                transform: `rotate(${(Math.atan(lineRectHeight / lineRectWidth) / Math.PI) * 180 + (lineRectWidth < 0 ? 180 : 0)}deg)`,
                transformOrigin: '0 0',
              }}
            />
          )
        ),
        relatedTag.element,
      )}
    </>
  );
};
